package com.zyw.codegen;

import com.zyw.codegen.java.ControllerGeneratorPlugin;
import com.zyw.codegen.java.DaoGenerator;
import com.zyw.codegen.java.ServiceGeneratorPlugin;
import com.zyw.mvc.servlet.BaseController;
import com.zyw.mvc.support.BaseService;
import com.zyw.orm.mybatis.MybatisBaseDao;
import com.zyw.orm.support.BaseEntity;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.api.ProgressCallback;
import org.mybatis.generator.api.ShellCallback;
import org.mybatis.generator.config.*;
import org.mybatis.generator.exception.InvalidConfigurationException;
import org.mybatis.generator.internal.DefaultShellCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author zhangyw
 * @date 2017/3/13 14:47
 */
public class Codegen {

    private static Logger logger = LoggerFactory.getLogger(Codegen.class);

    private static final String PACKAGE_DAO = "dao";
    private static final String PACKAGE_PO = "po";

    private String contextId;

    private String targetRuntime;

    private String dataSourceLookupKey;

    private GenConfig genConfig;

    private List<GenTable> tables;

    public Codegen() {
        this.contextId = "mysql";
        this.targetRuntime = IntrospectedTableMyBatis3CustomImpl.class.getName();
        this.tables = new ArrayList<GenTable>();
    }

    public void setContextId(String contextId) {
        this.contextId = contextId;
    }

    public void setDataSourceLookupKey(String dataSourceLookupKey) {
        this.dataSourceLookupKey = dataSourceLookupKey;
    }

    public void setGenConfig(GenConfig genConfig) {
        this.genConfig = genConfig;
    }

    public void setTables(List<GenTable> tables) {
        this.tables.addAll(tables);
    }

    public void addTable(GenTable table) {
        this.tables.add(table);
    }

    public String buildJavaTargetProject() {
        return genConfig.getJavasourcePath();
    }

    public String buildResourcesTargetProject() {
        return genConfig.getResourcePath();
    }

    private ConnectionFactoryConfiguration getConnectionFactory() {
        ConnectionFactoryConfiguration connectionFactoryConfiguration = new ConnectionFactoryConfiguration();
        connectionFactoryConfiguration.setConfigurationType(DataSourceConnectionFactory.class.getName());
        connectionFactoryConfiguration.addProperty("lookupKey", dataSourceLookupKey);
        return connectionFactoryConfiguration;
    }

    /**
     * 注释配置
     *
     * @return
     */
    private CommentGeneratorConfiguration getCommentGenerator() {
        CommentGeneratorConfiguration commentGenerator = new CommentGeneratorConfiguration();
        commentGenerator.addProperty("suppressAllComments", "true");
        commentGenerator.addProperty("suppressDate", "true");
        return commentGenerator;
    }

    private JavaTypeResolverConfiguration getJavaTypeResolver() {
        JavaTypeResolverConfiguration javaTypeResolverConfiguration = new JavaTypeResolverConfiguration();
        javaTypeResolverConfiguration.addProperty("forceBigDecimals", "false");
        return javaTypeResolverConfiguration;
    }

    /**
     * po 类生成
     *
     * @return
     */
    private JavaModelGeneratorConfiguration getJavaModelGenerator() {
        JavaModelGeneratorConfiguration javaModelGenerator = new JavaModelGeneratorConfiguration();
        javaModelGenerator.setTargetProject(buildJavaTargetProject());
        String packageName = genConfig.getPackageName();
        if (!packageName.endsWith(PACKAGE_PO))
            packageName = packageName.concat(".").concat(PACKAGE_PO);
        javaModelGenerator.setTargetPackage(packageName);
        /** 是否追加构造函数 */
        javaModelGenerator.addProperty("constructorBased", "false");
        /** 是否允许子包 */
        javaModelGenerator.addProperty("enableSubPackages", "false");
        /** 是否创建 getter setter */
        javaModelGenerator.addProperty("immutable", "false");
        /** 是否对string类型的数据，进行trim操作  */
        javaModelGenerator.addProperty("trimStrings", "true");
        javaModelGenerator.addProperty("rootClass", BaseEntity.class.getName());
        return javaModelGenerator;
    }

    /**
     * dao 文件生成
     *
     * @return
     */
    private JavaClientGeneratorConfiguration getJavaClientGenerator() {
        JavaClientGeneratorConfiguration clientGenerator = new JavaClientGeneratorConfiguration();
        String packageName = genConfig.getPackageName();
        if (!packageName.endsWith(PACKAGE_DAO))
            packageName = packageName.concat(".").concat(PACKAGE_DAO);
        clientGenerator.setTargetProject(buildJavaTargetProject());
        clientGenerator.setTargetPackage(packageName);
        clientGenerator.setConfigurationType(DaoGenerator.class.getName());

        clientGenerator.addProperty("enableSubPackages", "false");
        clientGenerator.addProperty("rootInterface", MybatisBaseDao.class.getName());

        return clientGenerator;
    }

    /**
     * 配置插件 ，配置生成service 代码插件
     *
     * @return
     */
    private PluginConfiguration getServicePluginGenerator() {
        PluginConfiguration pluginConfiguration = new PluginConfiguration();
        pluginConfiguration.setConfigurationType(ServiceGeneratorPlugin.class.getName());
        pluginConfiguration.addProperty("targetProject", buildJavaTargetProject());
        pluginConfiguration.addProperty("rootClass", BaseService.class.getName());
        return pluginConfiguration;
    }

    private PluginConfiguration getControllerPluginGenerator() {
        PluginConfiguration pluginConfiguration = new PluginConfiguration();
        pluginConfiguration.setConfigurationType(ControllerGeneratorPlugin.class.getName());
        pluginConfiguration.addProperty("targetProject", buildJavaTargetProject());
        pluginConfiguration.addProperty("rootClass", BaseController.class.getName());
        return pluginConfiguration;
    }

    private SqlMapGeneratorConfiguration getSqlMapGenerator() {
        SqlMapGeneratorConfiguration sqlMapGenerator = new SqlMapGeneratorConfiguration();
        String packageName = genConfig.getModuleName();
        sqlMapGenerator.setTargetPackage("mappings." + packageName);
        sqlMapGenerator.setTargetProject(buildResourcesTargetProject());

        sqlMapGenerator.addProperty("enableSubPackages", "false");
        sqlMapGenerator.addProperty("mergeable", genConfig.getXmlMergeable());
        return sqlMapGenerator;
    }

    private TableConfiguration createTableConfig(GenTable genTable, Context context) {
        TableConfiguration tc = new TableConfiguration(context);
        tc.setCatalog(genTable.getCatalog());
        tc.setSchema(genTable.getSchema());
        tc.setTableName(genTable.getTableName());
        tc.setDomainObjectName(genTable.entityClassName());
        tc.setMapperName(genTable.daoClassName());
        tc.setCountByExampleStatementEnabled(false);
        tc.setDeleteByExampleStatementEnabled(false);
        tc.setSelectByExampleStatementEnabled(false);
        tc.setUpdateByExampleStatementEnabled(false);
        return tc;
    }

    private MyBatisGenerator getMyBatisGenerator() throws InvalidConfigurationException {
        Context context = new Context(null);
        context.setId(this.contextId);
        context.setTargetRuntime(this.targetRuntime);
        context.addProperty(PropertyRegistry.CONTEXT_JAVA_FILE_ENCODING, "UTF-8");
        context.addProperty(PropertyRegistry.CONTEXT_BEGINNING_DELIMITER, "`");
        context.addProperty(PropertyRegistry.CONTEXT_ENDING_DELIMITER, "`");
        context.addProperty(PropertyRegistry.CONTEXT_AUTO_DELIMIT_KEYWORDS, "true");// mybatis xml 遇字段与关键字冲突，自动添加关键字反引号
        context.setConnectionFactoryConfiguration(getConnectionFactory());
        context.setCommentGeneratorConfiguration(getCommentGenerator());
        context.setJavaTypeResolverConfiguration(getJavaTypeResolver());
        context.setJavaModelGeneratorConfiguration(getJavaModelGenerator());
        context.setJavaClientGeneratorConfiguration(getJavaClientGenerator());
        context.setSqlMapGeneratorConfiguration(getSqlMapGenerator());
        context.addPluginConfiguration(getServicePluginGenerator());
        context.addPluginConfiguration(getControllerPluginGenerator());

        for (GenTable genTable : tables)
            context.addTableConfiguration(createTableConfig(genTable, context));

        Configuration configuration = new Configuration();
        configuration.addContext(context);

        ShellCallback shellCallback = new DefaultShellCallback(genConfig.isOverwriteEnabled());

        return new MyBatisGenerator(configuration, shellCallback, null);
    }

    public void generate() {
        try {
            MyBatisGenerator myBatisGenerator = getMyBatisGenerator();
            ProgressCallback callback = new ProgressCallback() {
                @Override
                public void introspectionStarted(int totalTasks) {
                    logger.info("execute totalTasks : {}", totalTasks);
                }

                @Override
                public void generationStarted(int totalTasks) {
                    logger.info("execute started totalTasks : {}", totalTasks);
                }

                @Override
                public void saveStarted(int totalTasks) {
                    logger.info("execute start totalTasks : {}", totalTasks);
                }

                @Override
                public void startTask(String taskName) {
                    logger.info("execute taskName : {}", taskName);
                }

                @Override
                public void done() {
                    logger.info("execute done");
                }

                @Override
                public void checkCancel() throws InterruptedException {

                }
            };
            GeneratorContextHolder.setGenConfig(genConfig);
            GeneratorContextHolder.setProgressCallback(callback);
            myBatisGenerator.generate(callback);
        } catch (InvalidConfigurationException e) {
            logger.error("mybatis generate configuration has throw an error {}", ExceptionUtils.getStackTrace(e));
        } catch (SQLException | IOException | InterruptedException e) {
            logger.error("mybatis generate process has throw an error {}", ExceptionUtils.getStackTrace(e));
        } finally {
            GeneratorContextHolder.clear();
            if (tables != null) {
                tables.clear();
            }
        }
    }
}
